/*                                    _
 *   ___ _ __ ___ _ __ ___   ___   __| |
 *  / __| '__/ __| '_ ` _ \ / _ \ / _` |
 *  \__ \ |  \__ \ | | | | | (_) | (_| |
 *  |___/_|  |___/_| |_| |_|\___/ \__,_|                                  
 *
 *	                   _                      
 *	 _  _  _  __ o __ (_|   __  _  _|    |  _ 
 *	_> (_ (_) |  | | |__|   |||(_)(_||_| | (/_
 *
 *                        
 ******************************************
 ******************************************
 ******************************************
 * . _ _  _ | _  _ _  _  _ _|_ _ _|_. _  _
 * || | ||_)|(/_| | |(/_| | | (_| | |(_)| |
 *       |                                 
 *  	  To make sure Players only see the Scores we compute, the Round End Score Display
 *			  gets hidden as soon as it shows itself. Then the Plugin dynamically displays
 *			  scoring items and what score the Survivors (and/or Infected) just earned
 * 
 * 		  Execution of the Scoring itself is simple. Hook Round End, check Survivors for
 * 			  still being alive (in the Saferoom), read their health and items, calculate
 * 			  the score. The Infected Damage can be transformed into a Score for them aswell.
 * 
 */
#define MODULE_NAME "ScoringSystem"

#define				STANDARD_STRINGLENGTH				  128
#define				SCORE_MODULE_STRINGLENGHT			  64

static const		SCORING_OUTPUT_ITEMS_PER_LINE		= 3;

static const		GETSCORE_SECOND_ARGUMENT			= 1;
static const		GETSCORE_OFFSET_SURVIVOR_ROUND		= 3;
static const		GETSCORE_OFFSET_INFECTED_ROUND		= 5;

static const		WEAPON_SLOT_THROWABLES				= 2;
static const		WEAPON_SLOT_DEPLOYABLES				= 3;
static const		WEAPON_SLOT_CONSUMABLES				= 4;

static const		L4D2_INFLICTOR_INFECTED				= 4095;

static const Float:ROUND_END_HIDE_SCORES_DELAY			= 0.1;
static const Float:ROUND_END_SHOW_TOTAL_DELAY			= 3.0;
static const Float:PHYSICS_DAMAGE_REDUCE_FACTOR			= 0.8;

static const String:SCORING_OUTPUT_PREFIX[]				= "\x04[SRS Score]\x01";

static const String:CVAR_L4D2_MANAGERPLUGIN[]			= "l4d2_team_manager_ver";
static const String:PLUGINFILE_L4D2_MANAGERPLUGIN[] 	= "l4d2scores";
static const String:GAMECONFIG_FILE[]					= "srsmod";

static const String:ENTITY_CLASS_MEDKIT[]				= "weapon_first_aid_kit";
static const String:ENTITY_CLASS_DEFIB[]				= "weapon_defibrillator";
static const String:ENTITY_CLASS_PILLS[]				= "weapon_pain_pills";
static const String:ENTITY_CLASS_ADRENALINE[]			= "weapon_adrenaline";
static const String:ENTITY_CLASS_MOLOTOV[]				= "weapon_molotov";
static const String:ENTITY_CLASS_PIPE_BOMB[]			= "weapon_pipe_bomb";
static const String:ENTITY_CLASS_VOMIT_JAR[]			= "weapon_vomitjar";
static const String:ENTITY_CLASS_EXPLOSIVE_AMMO[]		= "weapon_upgradepack_explosive";
static const String:ENTITY_CLASS_INCENDIARY_AMMO[]		= "weapon_upgradepack_incendiary";
static const String:ENTITY_CLASS_PROP_DAMAGER[]			= "prop_physics";

static const String:CVAR_REVIVE_HEALTH[]				= "survivor_revive_health";
static const String:CVAR_SURVIVAL_BONUS[]				= "vs_survival_bonus";
static const String:CVAR_TIE_BREAKER[]					= "vs_tiebreak_bonus";
static const String:ENTPROP_CURRENT_REVIVE_COUNT[]		= "m_currentReviveCount";

static const String:CONSOLE_COMMAND_VOTE[]				= "callvote";
static const String:EVENT_STRING_CHECKPOINT[]			= "checkpoint";
static const String:EVENT_STRING_VOTE_DETAILS[] 		= "details";
static const String:EVENT_STRING_VOTE_PARAM1[] 			= "param1";
static const String:EVENT_STRING_VOTENAME_BACK2LOB[] 	= "ReturnToLobby";
static const String:EVENT_STRING_VOTENAME_CHNGMISS[] 	= "ChangeMission";
static const String:EVENT_STRING_VOTE_MISSIONCHANGE[] 	= "#L4D_vote_passed_mission_change";
static const String:EVENT_STRING_VOTE_BACK_TO_LOBBY[] 	= "#L4D_vote_passed_return_to_lobby";

static 		Handle:cvarScoringEnabled					= INVALID_HANDLE;
static 		Handle:cvarSurvivalBonus					= INVALID_HANDLE;
static 		Handle:cvarTieBreaker						= INVALID_HANDLE;
static		Handle:cvarScoreInfected					= INVALID_HANDLE;
static 		Handle:fHideScoreboard						= INVALID_HANDLE;
static 		Handle:fAreTeamsFlipped						= INVALID_HANDLE;
static 		Handle:fGetTeamScore						= INVALID_HANDLE;

static 				Address:g_pDirector 				= Address:0;

enum itemInfos
{
			count,
			itemslot,
	Float:	score,
	String:	translation[SCORE_MODULE_STRINGLENGHT],
	String:	output[SCORE_MODULE_STRINGLENGHT],
	String:	entclass[SCORE_MODULE_STRINGLENGHT],
	Handle:	controlcvar
}

enum items
{
			survivors,
			hardhealth,
			temphealth,
			medkit,
			defib,
			pills,
			adrenaline,
			pipebomb,
			molotov,
			vomitjar, 
			explosiveammo,
			incendiaryammo,
			revives
}

static scoringitemdata[items][itemInfos];


static bool:eventsHooked					= false;
static bool:gameRoundEnded					= false;
static bool:missionChangingVote				= false;
static bool:roundEndHandled					= false;

static 		g_currentSurvivorScoreToAdd		= 0;
static		g_currentInfectedScoreToAdd		= 0;
static 		g_currentModBonusScore[2]		= 0;
static 		g_currentCampainScores[2]		= 0;

static Float:g_currentInfectedPhysicsDamage	= 0.0;


public Scoring_OnModuleLoaded()
{
	cvarScoringEnabled =							CreateConVar("srs_scoring_enabled", 		"1", 		"Enable the Scoring Module.",							SRS_CVAR_DEFAULT_FLAGS);

	scoringitemdata[survivors]		[controlcvar] =	CreateConVar("srs_score_survivor",	 		"100.0", 	"How much Score does a saved Survivor yield.",			SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[hardhealth]		[controlcvar] =	CreateConVar("srs_score_hard_health", 		"1.0", 		"How much Score does a full Health Point yield.",		SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[temphealth]		[controlcvar] =	CreateConVar("srs_score_temp_health", 		"0.5", 		"How much Score does a temporary Health Point yield.",	SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[medkit]			[controlcvar] = CreateConVar("srs_score_medkit", 			"75.0", 	"How much Score does a Medkit yield.",					SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[defib]			[controlcvar] = CreateConVar("srs_score_defib", 			"75.0", 	"How much Score does a Defibrillator yield.",			SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[pills]			[controlcvar] = CreateConVar("srs_score_pills", 			"30.0",		"How much Score do Pills yield.",						SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[adrenaline]		[controlcvar] = CreateConVar("srs_score_adrenaline", 		"25.0",		"How much Score does Adrenaline yield.",				SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[pipebomb]		[controlcvar] = CreateConVar("srs_score_pipebomb", 			"15.0", 	"How much Score does a Pipe Bomb yield.",				SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[molotov]		[controlcvar] = CreateConVar("srs_score_molotov", 			"15.0", 	"How much Score does a Molotov yield.",					SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[vomitjar]		[controlcvar] = CreateConVar("srs_score_vomitjar", 			"25.0", 	"How much Score does a Vomit Jar yield.",				SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[explosiveammo]	[controlcvar] = CreateConVar("srs_score_explosiveamo", 		"25.0", 	"How much Score does an Explosive Ammopack yield.",		SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[incendiaryammo]	[controlcvar] = CreateConVar("srs_score_incendiaryammo", 	"25.0", 	"How much Score does an Incendiary Ammopack yield.",	SRS_CVAR_DEFAULT_FLAGS);
	scoringitemdata[revives]		[controlcvar] = CreateConVar("srs_score_incap_malus",		"-33.0", 	"How much Score do Survivors lose per suffered Incap.",	SRS_CVAR_DEFAULT_FLAGS);
	cvarScoreInfected							  = CreateConVar("srs_score_infecteddamage", 	"0.0", 		"How much Score Infected get per Damagepoint dealt",	SRS_CVAR_DEFAULT_FLAGS);
	
	cvarSurvivalBonus = 	FindConVar(CVAR_SURVIVAL_BONUS);
	cvarTieBreaker =		FindConVar(CVAR_TIE_BREAKER);
	
	PrepareSDKCalls();
	WriteStaticDataIntoArray();

	_SC_OnModuleEnabled();
	HookConVarChange(cvarScoringEnabled, _SC_FlipActivation);
	
	RegConsoleCmd(CONSOLE_COMMAND_VOTE, _SC_Callvote_Handler);
	
	if (FindConVar(CVAR_L4D2_MANAGERPLUGIN) != INVALID_HANDLE) // L4D2 Team Manager was loaded before this
	{
        ServerCommand("sm plugins unload %s", PLUGINFILE_L4D2_MANAGERPLUGIN);
        CreateTimer(ROUND_END_HIDE_SCORES_DELAY, _SC_ReloadL4D2Scores);
	}
}

public Action:_SC_ReloadL4D2Scores(Handle:timer)
{
    ServerCommand("sm plugins load %s", PLUGINFILE_L4D2_MANAGERPLUGIN);
}

public _SC_KeepScoreConVarsZero(Handle:convar, const String:oldValue[], const String:newValue[])
{
	if (StringToInt(newValue))
	{
		SetConVarInt(cvarSurvivalBonus, 0);
		SetConVarInt(cvarTieBreaker, 	0);
	}
}

public _SC_FlipActivation(Handle:convar, const String:oldValue[], const String:newValue[])
{
	if (ACTIVATION_FLIP_OFF_TO_ON)
	{
		_SC_OnModuleEnabled();
	}
	else if (ACTIVATION_FLIP_ON_TO_OFF)
	{
		_SC_OnModuleDisabled();
	}
}

static _SC_OnModuleEnabled()
{
	HookEvent("round_start", 				_SC_RoundStart_Event, 			EventHookMode_PostNoCopy);
	HookEvent("round_end", 					_SC_RoundEnd_Event, 			EventHookMode_PostNoCopy);
	HookEvent("finale_vehicle_leaving", 	_SC_FinaleVehicleLeaving_Event, EventHookMode_PostNoCopy);
	HookEvent("door_close", 				_SC_DoorClosed_Event,			EventHookMode_Post);
	HookEvent("vote_passed", 				_SC_EventVoteEndSuccess,		EventHookMode_Post);
	HookEvent("vote_failed", 				_SC_EventVoteEndFail, 			EventHookMode_Post);
	
	SetConVarInt(cvarSurvivalBonus, 0);
	SetConVarInt(cvarTieBreaker, 	0);
	HookConVarChange(cvarSurvivalBonus,	 _SC_KeepScoreConVarsZero);
	HookConVarChange(cvarTieBreaker,	 _SC_KeepScoreConVarsZero);
	
	eventsHooked = true;
}

static _SC_OnModuleDisabled()
{
	if (!IsPluginEnding() && eventsHooked)
	{
		UnhookEvent("round_start", 				_SC_RoundStart_Event, 			EventHookMode_PostNoCopy);
		UnhookEvent("round_end", 				_SC_RoundEnd_Event, 			EventHookMode_PostNoCopy);
		UnhookEvent("finale_vehicle_leaving", 	_SC_FinaleVehicleLeaving_Event, EventHookMode_PostNoCopy);
		UnhookEvent("door_close", 				_SC_DoorClosed_Event, 			EventHookMode_Post);
		UnhookEvent("vote_passed", 				_SC_EventVoteEndSuccess,		EventHookMode_Post);
		UnhookEvent("vote_failed", 				_SC_EventVoteEndFail, 			EventHookMode_Post);
		eventsHooked = false;
	}
	
	UnhookConVarChange(cvarSurvivalBonus, 	_SC_KeepScoreConVarsZero);
	UnhookConVarChange(cvarTieBreaker, 		_SC_KeepScoreConVarsZero);
	ResetConVar(cvarSurvivalBonus);
	ResetConVar(cvarTieBreaker);
}

public _SC_OnTakeDamage(victim, attacker, inflictor, Float:damage)
{
	if (inflictor != L4D2_INFLICTOR_INFECTED
	|| !IsValidEdict(attacker)
	|| !IsValidEdict(victim)
	|| victim > MaxClients
	|| !IsClientInGame(victim)
	|| L4D2_GetClientTeam(victim) != L4D2Team_Survivors)
	{
		return;
	}
	
	decl String:classname[SCORE_MODULE_STRINGLENGHT];
	GetEdictClassname(attacker, classname, sizeof(classname));
	
	if (StrEqual(classname, ENTITY_CLASS_PROP_DAMAGER))
	{
		Debug_Print("Car/Trashbin Damage of %i on Survivor %N detected", RoundToNearest(damage), victim);
		g_currentInfectedPhysicsDamage += (damage * PHYSICS_DAMAGE_REDUCE_FACTOR);
	}
}

static PrepareSDKCalls()
{
	new Handle:conf = LoadGameConfigFile(GAMECONFIG_FILE);
	
	if (conf == INVALID_HANDLE)
	{
		ThrowError("[SRSMOD] Failed to load %s.txt in gamedata folder", GAMECONFIG_FILE);
	}
	
	g_pDirector = GameConfGetAddress(conf, "CDirector");
	Debug_Print("PTR to Director loaded at 0x%x", g_pDirector);
	if(g_pDirector == Address_Null)
	{
		ThrowError("[SRSMOD] Could not load the Director pointer");
	}

	StartPrepSDKCall(SDKCall_Raw);
	PrepSDKCall_SetFromConf(conf, SDKConf_Signature, "HideScoreboard");
	fHideScoreboard = EndPrepSDKCall();
	
	if (fHideScoreboard == INVALID_HANDLE)
	{
		ThrowError("[SRSMOD] Could not prep HideScoreboard SDKCall");
	}
	
	StartPrepSDKCall(SDKCall_Raw);
	PrepSDKCall_SetFromConf(conf, SDKConf_Signature, "AreTeamsFlipped");
	PrepSDKCall_SetReturnInfo(SDKType_Bool, SDKPass_Plain);
	fAreTeamsFlipped = EndPrepSDKCall();
	
	if (fAreTeamsFlipped == INVALID_HANDLE)
	{
		ThrowError("[SRSMOD] Could not prep AreTeamsFlipped SDKCall");
	}
	
	StartPrepSDKCall(SDKCall_GameRules);
	PrepSDKCall_SetFromConf(conf, SDKConf_Signature, "GetTeamScore");
	PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
	PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain);
	PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
	fGetTeamScore = EndPrepSDKCall();
	
	if(fGetTeamScore == INVALID_HANDLE)
	{
		ThrowError("[SRSMOD] Could not prep GetTeamScore SDKCall");
	}
	
	CloseHandle(conf);
}

public Action:_SC_DoorClosed_Event(Handle:event, const String:name[], bool:dontBroadcast)
{
	if (GetEventBool(event, EVENT_STRING_CHECKPOINT))
	{
		Debug_Print("Door Closed Event - Checkpoint reached");
		gameRoundEnded = true;
	}
}

public Action:_SC_FinaleVehicleLeaving_Event(Handle:event, const String:name[], bool:dontBroadcast)
{
	Debug_Print("Finale Vehicle Leaving - Round Ended");
	gameRoundEnded = true;
}

public Action:_SC_RoundStart_Event(Handle:event, const String:name[], bool:dontBroadcast)
{
	roundEndHandled = false;
}

public Action:_SC_RoundEnd_Event(Handle:event, const String:name[], bool:dontBroadcast)
{	
	new survivorcount = 0;
	FOR_EACH_ALIVE_SURVIVOR(i)
	{
		if (!IsSurvivorIncapacitated(i))
		{
			survivorcount ++;
		}
	}
	
	if(roundEndHandled)
	{
		roundEndHandled = false;
		Debug_Print("Double Round End Prevented");
		return;
	}
	
	roundEndHandled = true;
	
	if (!survivorcount && !RoundStatus_IsBeforeMapStart() && IsAllowedGameMode())
	{
		
		Debug_Print("No Survivors left standing detected - Infected won the Round, Survivor Bonus remains zero");
		PrintToChatAll("\x04[SRS Score]\x01 %t \x040", "EpicFail");
		
		g_currentInfectedScoreToAdd = RoundToNearest((L4D2_GetInfectedTeamRoundScore() - g_currentInfectedPhysicsDamage) * GetConVarFloat(cvarScoreInfected));
		Debug_Print("Round Infected brutto Damage: %i, Car damage: %i, scoretoadd: %i", L4D2_GetInfectedTeamRoundScore(), RoundToNearest(g_currentInfectedPhysicsDamage), g_currentInfectedScoreToAdd);
		
		CreateTimer(ROUND_END_HIDE_SCORES_DELAY, _SC_HideScoreBoard_Timer);
		
		CreateTimer(ROUND_END_SHOW_TOTAL_DELAY, _SC_ShowCampaignScores_Timer);
	}
}

public Action:_SC_HideScoreBoard_Timer(Handle:timer)
{
	SDKCall(fHideScoreboard, g_pDirector);
}

public Action:L4D_OnSetCampaignScores(&scoreA, &scoreB)
{
	if (!scoreA && !scoreB && RoundStatus_IsBeforeMapStart())
	{
		OnNewMission();
	}

	if (gameRoundEnded && IsAllowedGameMode())
	{
		gameRoundEnded = false;
		CalculateCurrentTeamScores();
		CreateTimer(ROUND_END_HIDE_SCORES_DELAY, _SC_HideScoreBoard_Timer);
	}

	if (g_currentSurvivorScoreToAdd)
	{
		g_currentModBonusScore[L4D2_AreTeamsFlipped()] += g_currentSurvivorScoreToAdd;
		g_currentSurvivorScoreToAdd = 0;
	}
	
	if (g_currentInfectedScoreToAdd)
	{
		g_currentModBonusScore[!(L4D2_AreTeamsFlipped())] += g_currentInfectedScoreToAdd;
		g_currentInfectedScoreToAdd = 0;
	}
	
	scoreA += g_currentModBonusScore[0];
	scoreB += g_currentModBonusScore[1];
	
	g_currentCampainScores[0] = scoreA;
	g_currentCampainScores[1] = scoreB;
	
	return Plugin_Continue;
}

public Action:L4D_OnClearTeamScores(bool:newCampaign)
{
	if (newCampaign)
	{
		OnNewMission();
	}
}

static OnNewMission()
{
	Debug_Print("OnNewMission called, wiping Bonus Score Array");

	g_currentModBonusScore[0] = 0;
	g_currentModBonusScore[1] = 0;
}

static CalculateCurrentTeamScores()
{
	Debug_Print("Calculating Team Score now");

	// reset counters to zero
	for (new i = 0; i < sizeof(scoringitemdata); i++)
	{
		scoringitemdata[i][count] = 0;
	}
	
	new incappedsurvivorhealth = RoundToNearest(GetConVarFloat(FindConVar(CVAR_REVIVE_HEALTH)));
	decl String:entityclass[STANDARD_STRINGLENGTH];
	new tempent = 0;
	new health = 0;
	new tmphealth = 0;
	new revivesread = 0;
	
	FOR_EACH_ALIVE_SURVIVOR(survivor)
	{
		revivesread = GetEntProp(survivor, Prop_Send, ENTPROP_CURRENT_REVIVE_COUNT);
		
		if (IsSurvivorIncapacitated(survivor))
		{
			if (L4D2_IsFinaleMapNow())
			{
				continue; // Incapped Survivors dont count in the finale
			}
			else
			{
				revivesread++;
				Debug_Print("Found an Incapped Survivor in the Saferoom, adding %f temp health, %i revives and calculating items", incappedsurvivorhealth, revivesread);
				scoringitemdata[temphealth][count] += incappedsurvivorhealth;
			}
		}
		else
		{
			health = L4D2_GetEntityHealth(survivor);
			tmphealth = L4D2_GetAccurateTempHealth(survivor);
			scoringitemdata[revives][count] += GetEntProp(survivor, Prop_Send, ENTPROP_CURRENT_REVIVE_COUNT);
			
			Debug_Print("Found a Standing Survivor, calculating his score, health %i temphealth %i revives %i", health, tmphealth, revivesread);
			scoringitemdata[hardhealth][count] += health;
			scoringitemdata[temphealth][count] += tmphealth;
		}
		
		scoringitemdata[survivors][count]++;
		scoringitemdata[revives][count] +=  revivesread;
		
		Debug_Print("Now checking Survivor %Ns pockets", survivor);
		for (new j = WEAPON_SLOT_THROWABLES; j <= WEAPON_SLOT_CONSUMABLES; j++) // loop all itemslots
		{
			tempent = GetPlayerWeaponSlot(survivor, j); // get weapon of slot
			if (!IsValidEntity(tempent))
			{
				continue; // go to next slot if no item present
			}
			
			GetEdictClassname(tempent, entityclass, sizeof(entityclass));
			Debug_Print("Found item %s", entityclass);
			
			for (new w = 0; w < sizeof(scoringitemdata); w++) // now loop items
			{
				if (scoringitemdata[w][itemslot] == j) // compare slot
				{
					if (StrEqual(entityclass, scoringitemdata[w][entclass])) // compare entclass
					{
						scoringitemdata[w][count]++; // add count if necessary
					}
				}
			}
		}
	}
	
	Debug_Print("Finished reading Health and Items, calculating Scores now...");
	
	PrintToChatAll("\x04[SRS Score]\x01 %t", "Scoretext"); // "Scores gained, by items:"
	
	new Float:roundscore = 0.0;
	new itemsOnChatLine = 0;
	new String:outputString[STANDARD_STRINGLENGTH] = "";
	
	for (new i = 0; i < sizeof(scoringitemdata); i++)
	{
		if (!scoringitemdata[i][count] || GetConVarFloat(scoringitemdata[i][controlcvar]) == 0.0)
		{
			continue; // disregard things not involved in scoring
		}
		
		scoringitemdata[i][score] = GetConVarFloat(scoringitemdata[i][controlcvar]) * scoringitemdata[i][count]; // compute total score per item multiplying convar and count
		
		roundscore += scoringitemdata[i][score]; // add total score per item to total score
		
		Format(scoringitemdata[i][output], SCORE_MODULE_STRINGLENGHT-1, "%t: \x04%i\x01 ", scoringitemdata[i][translation], RoundToFloor(scoringitemdata[i][score]));
		Debug_Print("Formatted input String, now: %s", scoringitemdata[i][output]);
		
		// add String to the chat line were cooking
		Format(outputString, STANDARD_STRINGLENGTH-1, "%s %s", outputString, scoringitemdata[i][output]);
		itemsOnChatLine++;
		Debug_Print("Items: %i, Output String: %s", itemsOnChatLine, outputString);
		
		if (itemsOnChatLine >= SCORING_OUTPUT_ITEMS_PER_LINE)
		{
			// add the prefix for a nice touch
			Format(outputString, STANDARD_STRINGLENGTH-1, "%s%s", SCORING_OUTPUT_PREFIX, outputString);
			PrintToChatAll("%s", outputString);
			Debug_Print("Printed and flushed Loop Output String");
			Format(outputString, STANDARD_STRINGLENGTH-1, "");
			itemsOnChatLine = 0;
		}
	}
	Debug_Print("Loop done, Chat items: %i, String: %s", itemsOnChatLine, outputString);
	if (itemsOnChatLine) // print anything left over
	{
		// add the prefix for a nice touch
		Format(outputString, STANDARD_STRINGLENGTH-1, "%s%s", SCORING_OUTPUT_PREFIX, outputString);
		PrintToChatAll("%s", outputString);
		Debug_Print("Printed leftover Output String");
	}
	
	new survivordistancescore = L4D2_GetSurvivorTeamRoundScore();
	new bonusscore = RoundToNearest(roundscore);
	
	Debug_Print("Survivors: %i, Pooled Hard Health: %i, Pooled Temp Health: %i", scoringitemdata[survivors][count], scoringitemdata[hardhealth][count], scoringitemdata[temphealth][count]);
	Debug_Print("Medkits: %i, Defibs: %i, Pills: %i", scoringitemdata[medkit][count], scoringitemdata[defib][count], scoringitemdata[pills][count]);
	Debug_Print("Adrenaline: %i, Pipes: %i, Molos: %i", scoringitemdata[adrenaline][count], scoringitemdata[pipebomb][count], scoringitemdata[molotov][count]);
	Debug_Print("Bilebombs: %i, Explosive Ammopacks: %i, Incend. Ammopacks: %i", scoringitemdata[vomitjar][count], scoringitemdata[explosiveammo][count], scoringitemdata[incendiaryammo][count]);
	Debug_Print("Round Survivor total Bonus Score %f", roundscore);
	Debug_Print("Round Survivor Distance Score: %i, Total Score this Round: %i", survivordistancescore, bonusscore + survivordistancescore);
	Debug_Print("Round Infected brutto Damage: %i, Car damage: %i", L4D2_GetInfectedTeamRoundScore(), RoundToNearest(g_currentInfectedPhysicsDamage));
	
	// Print Total Round Bonus
	PrintToChatAll("%s %t \x04%i", SCORING_OUTPUT_PREFIX, "TotalRoundBonus", bonusscore);
	// Print Distance Score (L4D2 Default)
	PrintToChatAll("%s %t \x04%i", SCORING_OUTPUT_PREFIX, "DistanceScore", survivordistancescore);
	// Summarize
	PrintToChatAll("%s %t \x04%i", SCORING_OUTPUT_PREFIX, "SurvScoreThisRound", bonusscore + survivordistancescore);
	
	// notify the score hook to do the changes
	g_currentSurvivorScoreToAdd = bonusscore;
	g_currentInfectedScoreToAdd = RoundToNearest((L4D2_GetInfectedTeamRoundScore() - g_currentInfectedPhysicsDamage) * GetConVarFloat(cvarScoreInfected));
	
	// to display current campaign scores and round infected scores
	CreateTimer(ROUND_END_SHOW_TOTAL_DELAY, _SC_ShowCampaignScores_Timer);
}

public Action:_SC_ShowCampaignScores_Timer(Handle:timer)
{
	new swap = L4D2_AreTeamsFlipped();
	new infscore = L4D2_GetInfectedTeamRoundScore();
	
	PrintToChatAll("%s %t \x04%i [with physics: %i]", SCORING_OUTPUT_PREFIX, "InfectedDamage", RoundToNearest(infscore - g_currentInfectedPhysicsDamage), infscore);
	g_currentInfectedPhysicsDamage = 0.0;
	
	new Float:infmultiplier = GetConVarFloat(cvarScoreInfected);
	if (infmultiplier > 0.0)
	{
		PrintToChatAll("%s %t \x04%i", SCORING_OUTPUT_PREFIX, "InfectedScores", RoundToNearest((infscore - g_currentInfectedPhysicsDamage) * infmultiplier));
	}
	
	PrintToChatAll("%s %t \x04%i \x01: \x04%i", SCORING_OUTPUT_PREFIX, "CurrentCampaignScores", swap ? g_currentCampainScores[1] : g_currentCampainScores[0], swap ? g_currentCampainScores[0] : g_currentCampainScores[1]);
}

static L4D2_AreTeamsFlipped()
{
	return SDKCall(fAreTeamsFlipped, g_pDirector);
}

static L4D2_GetSurvivorTeamRoundScore()
{
	return SDKCall(fGetTeamScore, (GETSCORE_OFFSET_SURVIVOR_ROUND + L4D2_AreTeamsFlipped()), GETSCORE_SECOND_ARGUMENT);
}

static L4D2_GetInfectedTeamRoundScore()
{
	new infteam = !(L4D2_AreTeamsFlipped());
	return SDKCall(fGetTeamScore, (GETSCORE_OFFSET_INFECTED_ROUND + infteam), GETSCORE_SECOND_ARGUMENT);
}

static WriteStaticDataIntoArray()
{
	Format(scoringitemdata[survivors]		[translation], SCORE_MODULE_STRINGLENGHT-1, "SurvivorsSafe");
	Format(scoringitemdata[hardhealth]		[translation], SCORE_MODULE_STRINGLENGHT-1, "HealthSafe");
	Format(scoringitemdata[temphealth]		[translation], SCORE_MODULE_STRINGLENGHT-1, "TempHealthSafe");
	Format(scoringitemdata[medkit]			[translation], SCORE_MODULE_STRINGLENGHT-1, "Medkits");
	Format(scoringitemdata[defib]			[translation], SCORE_MODULE_STRINGLENGHT-1, "Defibs");
	Format(scoringitemdata[pills]			[translation], SCORE_MODULE_STRINGLENGHT-1, "Pills");
	Format(scoringitemdata[adrenaline]		[translation], SCORE_MODULE_STRINGLENGHT-1, "Adrenaline");
	Format(scoringitemdata[pipebomb]		[translation], SCORE_MODULE_STRINGLENGHT-1, "PipeBombs");
	Format(scoringitemdata[molotov]			[translation], SCORE_MODULE_STRINGLENGHT-1, "Molotovs");
	Format(scoringitemdata[vomitjar]		[translation], SCORE_MODULE_STRINGLENGHT-1, "VomitJars");
	Format(scoringitemdata[explosiveammo]	[translation], SCORE_MODULE_STRINGLENGHT-1, "ExplosiveAmmo");
	Format(scoringitemdata[incendiaryammo]	[translation], SCORE_MODULE_STRINGLENGHT-1, "IncendiaryAmmo");
	Format(scoringitemdata[revives]			[translation], SCORE_MODULE_STRINGLENGHT-1, "IncapRevives");
	
	scoringitemdata[survivors]		[itemslot] 	= 0;
	scoringitemdata[hardhealth]		[itemslot] 	= 0;
	scoringitemdata[temphealth]		[itemslot] 	= 0;
	scoringitemdata[medkit]			[itemslot] 	= WEAPON_SLOT_DEPLOYABLES;
	scoringitemdata[defib]			[itemslot] 	= WEAPON_SLOT_DEPLOYABLES;
	scoringitemdata[pills]			[itemslot] 	= WEAPON_SLOT_CONSUMABLES;
	scoringitemdata[adrenaline]		[itemslot] 	= WEAPON_SLOT_CONSUMABLES;
	scoringitemdata[pipebomb]		[itemslot] 	= WEAPON_SLOT_THROWABLES;
	scoringitemdata[molotov]		[itemslot] 	= WEAPON_SLOT_THROWABLES;
	scoringitemdata[vomitjar]		[itemslot] 	= WEAPON_SLOT_THROWABLES;
	scoringitemdata[explosiveammo]	[itemslot] 	= WEAPON_SLOT_DEPLOYABLES;
	scoringitemdata[incendiaryammo]	[itemslot] 	= WEAPON_SLOT_DEPLOYABLES;
	scoringitemdata[revives]		[itemslot] 	= 0;
	
	Format(scoringitemdata[medkit]			[entclass], SCORE_MODULE_STRINGLENGHT-1, ENTITY_CLASS_MEDKIT);
	Format(scoringitemdata[defib]			[entclass], SCORE_MODULE_STRINGLENGHT-1, ENTITY_CLASS_DEFIB);
	Format(scoringitemdata[pills]			[entclass], SCORE_MODULE_STRINGLENGHT-1, ENTITY_CLASS_PILLS);
	Format(scoringitemdata[adrenaline]		[entclass], SCORE_MODULE_STRINGLENGHT-1, ENTITY_CLASS_ADRENALINE);
	Format(scoringitemdata[pipebomb]		[entclass], SCORE_MODULE_STRINGLENGHT-1, ENTITY_CLASS_PIPE_BOMB);
	Format(scoringitemdata[molotov]			[entclass], SCORE_MODULE_STRINGLENGHT-1, ENTITY_CLASS_MOLOTOV);
	Format(scoringitemdata[vomitjar]		[entclass], SCORE_MODULE_STRINGLENGHT-1, ENTITY_CLASS_VOMIT_JAR);
	Format(scoringitemdata[explosiveammo]	[entclass], SCORE_MODULE_STRINGLENGHT-1, ENTITY_CLASS_EXPLOSIVE_AMMO);
	Format(scoringitemdata[incendiaryammo]	[entclass], SCORE_MODULE_STRINGLENGHT-1, ENTITY_CLASS_INCENDIARY_AMMO);
}

public Action:_SC_Callvote_Handler(client, args)
{
	decl String:voteName[STANDARD_STRINGLENGTH];
	GetCmdArg(1,voteName,sizeof(voteName));
	
	if ((StrEqual(voteName, EVENT_STRING_VOTENAME_BACK2LOB, false) || StrEqual(voteName, EVENT_STRING_VOTENAME_CHNGMISS, false)))
	{
		Debug_Print("Mission Changing Vote by %N caught", client);
		missionChangingVote = true;
	}
}

public _SC_EventVoteEndSuccess(Handle:event, const String:name[], bool:dontBroadcast)
{
	if (!missionChangingVote) return;
	
	decl String:details[STANDARD_STRINGLENGTH], String:param1[STANDARD_STRINGLENGTH];
	GetEventString(event, EVENT_STRING_VOTE_DETAILS, details, sizeof(details));
	GetEventString(event, EVENT_STRING_VOTE_PARAM1, param1, sizeof(param1));
	
	Debug_Print("Mission Changing Vote End caught, details: %s ; param1: %s ", details, param1);
	missionChangingVote = false;
	
	if (strcmp(details, EVENT_STRING_VOTE_MISSIONCHANGE, false) == 0)
	{
		Debug_Print("New Campaign Vote Success caught, executing OnNewMission()");
		OnNewMission();
	}
	
	if (strcmp(details, EVENT_STRING_VOTE_BACK_TO_LOBBY, false) == 0)
	{
		Debug_Print("Return To Lobby Vote Success caught, executing OnNewMission()");
		OnNewMission();
	}
}

public _SC_EventVoteEndFail(Handle:event, const String:name[], bool:dontBroadcast)
{
	if (missionChangingVote)
	{
		missionChangingVote = false;
	}
}

#undef MODULE_NAME